with Ada.Text_Io; use Ada.Text_Io;
with Ada.Integer_Text_Io; use Ada.Integer_Text_Io;
with Ada.Numerics.Float_Random;use Ada.Numerics.Float_Random;

package body Travellers is
    
    -- Traveller task implementation
    task body Traveller is
        visites: VisiteArrayPointer;
        nVisited: Integer;
        G : Generator;
        actual: Integer;
        distance: Integer;
    begin
        distance:=0;
        actual:=initial;
        -- Initialize visits array
        visites:=new VisiteArray(1..list.nNodes);
        nVisited:=1;
        for i in 1..list.nNodes loop
            visites(i):=false;
        end loop;
        visites(initial):=true;

        -- Initialize Random Generator
        Reset (G, initial);
 
       while nVisited<list.nNodes loop
                declare
                    candidate: Integer;
                    candidateID: Integer;
                    success: boolean;
                begin
                    success:=false;
                    -- trying to travel
                    while not success loop
                        
                        candidate:=1+Integer(Random(G)*Float(list.nodes(actual).links));
                        if candidate>list.nodes(actual).links then
                            candidate:=list.nodes(actual).links;
                        end if;
                        candidateID:=list.nodes(actual).linkList(candidate);
                       -- try to travel to candidate link
                        select
                            list.nodes(candidateID).occupation.ocupe;
                            success:=true;
                        or
                            delay 0.00001;
                        end select;
                    end loop;
                    -- Success
                    list.nodes(actual).occupation.leave;
                    actual:=candidateID;
                    distance:=distance+1;
                    -- Visites control
                    if not visites(actual) then
                        visites(actual):=true;
                        nVisited:=nVisited+1;
                    end if;
                end;
       end loop;
        -- leave last visited node
       list.nodes(actual).occupation.leave;
        -- Notify manager
       manager.RemoveTraveller(distance);
    end Traveller;
    
    -- Finds starting node for the new traveller
    function findFreeNode(list: NodeListPointer; initial: Integer) return Integer is
        i: Integer;
        G : Generator;
    begin
        -- init randomization
        Reset (G, initial);

        i:=Integer(Random(G)*Float(list.nNodes))+1;
        if i>list.nNodes then
            i:=list.nNodes;
        end if;
        
        -- try to minimize random impact
        while not list.nodes(i).occupation.isFree loop
            i:=Integer(Random(G)*Float(list.nNodes))+1;
            if i>list.nNodes then
                i:=list.nNodes;
            end if;
        end loop;
        -- infinite try to ocupe designated node
        list.nodes(i).occupation.ocupe;
        Put("Arranca en el node ");put(i);put_line("");
        return i;
    end findFreeNode;
            
   -- Global statistics console output
    procedure printGlobal(running: Integer; counter: Integer; maxRunning: Integer;totalTravellers: Integer; maxDistance: Integer; totalDistance: Integer) is
        
    begin
        put_line("");
        put("Viatgers generats: ");put(counter);put(" ");
        put(100*counter/totalTravellers);
        put("%");put_line("");
        
        put("Viatgers actius  : ");put(running);put_line("");
        
        if counter-running>0 then
            put("Distancia maxima : ");put(maxDistance);put_line("");
            put("Distancia promig : ");put(totalDistance/(counter-running));put_line("");
        end if;
        
  
    end printGlobal;
    
    -- Node statistics disc output
    procedure writeResult(list: NodeListPointer) is
        f: File_Type;
        nodesPrinted: Integer;
        actualGrade: Integer;
    begin
        Ada.Text_IO.Create(
            File => f,
            Name => "result.txt",
            Mode => Ada.Text_IO.Out_File);
        
        nodesPrinted:=0;
        actualGrade:=1;
        put_line(f,"");
        while nodesPrinted<list.nNodes loop
            for i in 1..list.nNodes loop
                if list.nodes(i).links=actualGrade then
                    put(f,"Node ");put(f,list.nodes(i).Name);put(f," - Grau ");put(f,list.nodes(i).links);
                    put(f," - visitat ");put(f,list.nodes(i).occupation.nTimesVisited); put_line(f," vegades");
                    nodesPrinted:=nodesPrinted+1;
                end if;
            end loop;
            actualGrade:=actualGrade+1;
        end loop;
        put_line(f,"");
        Ada.Text_IO.Close(File => f);

    end writeResult;
    
    --Node statistics console output
    procedure printNodeStats(list: NodeListPointer) is
        nodesPrinted: Integer;
        actualGrade: Integer;
    begin
        nodesPrinted:=0;
        actualGrade:=1;
        put_line("");
        while nodesPrinted<list.nNodes loop
            for i in 1..list.nNodes loop
                if list.nodes(i).links=actualGrade then
                    put("Node ");put(list.nodes(i).Name);put(" - Grau ");put(list.nodes(i).links);
                    put(" - visitat ");put(list.nodes(i).occupation.nTimesVisited); put_line(" vegades");
                    nodesPrinted:=nodesPrinted+1;
                end if;
            end loop;
            actualGrade:=actualGrade+1;
        end loop;
        put_line("");
    end printNodeStats;
    
--  Traveller Manager Implementation    
    
    task body TravellerManager is
        
        running: Integer:=0;
        counter: Integer:=0;
        myManager: TravellerManagerPointer;
        travellers: PTR_Array_Travellers;
        maxDistance: Integer:=0;
        totalDistance: Integer:=0;
        
     begin
        
        put("Xarxa de ");put(list.nNodes);put_line(" nodes");
        
        -- wait for main task to provide pointer to itself
        accept setManager(manager: TravellerManagerPointer) do
                myManager:=manager;
                Put_Line("Manager receives its pointer");
        end setManager;
        
        -- wait for main task to start travellers creation
        accept CreateTravellers do 
            travellers:= new Array_Travellers(1..totalTravellers);
            for i in 1..maxRunning loop
                travellers(i):=new Traveller(myManager, list, findFreeNode(list,counter));
                counter:=counter+1;
                running:=running+1;
                printGlobal(running,counter,maxRunning,totalTravellers, maxDistance, totalDistance);
            end loop;
            Put_Line("Travellers Runinng");
        end CreateTravellers;

        -- main loop until all finished
        while running>0 loop
            -- accept finish notifications
            accept RemoveTraveller(distance:Integer) do
                -- calc statistics from finished traveller
                totalDistance:=totalDistance+distance;
                if distance>maxDistance then
                    maxDistance:=distance;
                end if;
                if counter<totalTravellers then
                    travellers(counter):=new Traveller(myManager, list, findFreeNode(list,counter));
                    counter:=counter+1;
                else
                    -- Decreasing number of travellers
                    running:=running-1;
                end if;
            end RemoveTraveller;
            -- periodic monitoring
            printGlobal(running,counter,maxRunning,totalTravellers, maxDistance, totalDistance);
            printNodeStats(list);        
        end loop;
        -- disc output
        writeResult(list);
        put_line("Arribat al final de la simulacio");
            
    end TravellerManager;  
   
begin
    null;
end Travellers;